/*******************************************************************************
 * Copyright (c) 2006, 2015 Wind River Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *     Ericsson			  - Modified for additional features in DSF Reference Implementation
 *     Roland Grunberg (RedHat) - Refresh all registers once one is changed (Bug 400840)
 *     Alvaro Sanchez-Leon (Ericsson) - Make Registers View specific to a frame (Bug 323552)
 *     Alvaro Sanchez-Leon (Ericsson) - Register view does not refresh register names per process (Bug 418176)
 *     Alvaro Sanchez-Leon (Ericsson) - Allow user to edit the register groups (Bug 235747)
 *******************************************************************************/
package org.eclipse.cdt.dsf.mi.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.AbstractDMContext;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.ICachingService;
import org.eclipse.cdt.dsf.debug.service.IExpressions;
import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMContext;
import org.eclipse.cdt.dsf.debug.service.IRegisters;
import org.eclipse.cdt.dsf.debug.service.IRunControl;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.StateChangeReason;
import org.eclipse.cdt.dsf.debug.service.IStack;
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
import org.eclipse.cdt.dsf.debug.service.command.BufferedCommandControl;
import org.eclipse.cdt.dsf.debug.service.command.CommandCache;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.mi.service.command.CommandFactory;
import org.eclipse.cdt.dsf.mi.service.command.output.MIDataListRegisterNamesInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIDataListRegisterValuesInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIRegisterValue;
import org.eclipse.cdt.dsf.service.AbstractDsfService;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.osgi.framework.BundleContext;

/**
 * 
 * <p> 
 * Implementation note:
 * This class implements event handlers for the events that are generated by 
 * this service itself.  When the event is dispatched, these handlers will
 * be called first, before any of the clients.  These handlers update the 
 * service's internal state information to make them consistent with the 
 * events being issued.  Doing this in the handlers as opposed to when 
 * the events are generated, guarantees that the state of the service will
 * always be consistent with the events.
 */

public class MIRegisters extends AbstractDsfService implements IRegisters, ICachingService {
	/**
	 * @since 4.6
	 */
	protected static final String BLANK_STRING = ""; //$NON-NLS-1$
	/**
	 * @since 4.6
	 */
	protected static final String ROOT_GROUP_NAME = Messages.MIRegisters_General_Registers;
	/**
	 * @since 4.6
	 */
	protected static final String ROOT_GROUP_DESCRIPTION = Messages.MIRegisters_General_Registers_description;
    /*
     * Support class used to construct Register Group DMCs.
     */
	
    public static class MIRegisterGroupDMC extends AbstractDMContext implements IRegisterGroupDMContext {
        private final int fGroupNo;
		private final IContainerDMContext parent;
        private String fGroupName;

        public MIRegisterGroupDMC(MIRegisters service, IContainerDMContext contDmc, int groupNo, String groupName) {
            super(service.getSession().getId(), new IDMContext[] { contDmc });
			this.parent = contDmc;
            fGroupNo = groupNo;
            fGroupName = groupName;
        }

        public int getGroupNo() { return fGroupNo; }
        public String getName() { return fGroupName; }
        
        /**
		 * @since 5.3
		 */
        public IContainerDMContext getContainerParent() {
			return parent;
		}
        
        /**
		 * @since 4.6
		 */
		public void setName(String groupName) {
			fGroupName = groupName;
		}

        @Override
        public boolean equals(Object other) {
            return ((super.baseEquals(other)) && (((MIRegisterGroupDMC) other).fGroupNo == fGroupNo) && 
                    (((MIRegisterGroupDMC) other).fGroupName.equals(fGroupName)));
        }
        
        @Override
        public int hashCode() { return super.baseHashCode() ^ fGroupNo; }
        @Override
        public String toString() { return baseToString() + ".group[" + fGroupNo + "," + fGroupName + "]"; }             //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    }
       
    /*
     * Support class used to construct Register DMCs.
     */
    
    public static class MIRegisterDMC extends AbstractDMContext implements IRegisterDMContext {
        private int fRegNo;
        private String fRegName;

        /**
         * An MIRegiserDMC created with no frame context will not be able to resolve the associated register value.
         * However these instances are useful to provide the register name i.e. needed from the selection of a process,
         * or a running thread where it is not possible to associate registers to frames and therefore values.
         */
        public MIRegisterDMC(MIRegisters service, MIRegisterGroupDMC group, int regNo, String regName) {
            super(service.getSession().getId(), 
                    new IDMContext[] { group });
              fRegNo = regNo;
              fRegName = regName;
        }

        @Deprecated
        public MIRegisterDMC(MIRegisters service, MIRegisterGroupDMC group, IMIExecutionDMContext execDmc, int regNo, String regName) {
            super(service.getSession().getId(), 
                    new IDMContext[] { execDmc, group });
              fRegNo = regNo;
              fRegName = regName;
        }

		/**
		 * This Register context is associated to two parent contexts. A stack frame context (IFrameDMContext), and a
		 * register group context (MIRegisterGroupDMC). When the scenario requires to build a register contexts from the
		 * selection of a thread, then the top frame shall be resolved and be provided in this constructor.
		 * 
		 * The frame context is necessary to resolve the register's data e.g. value
		 * 
		 * @since 4.3
		 */
		public MIRegisterDMC(MIRegisters service, MIRegisterGroupDMC group, IFrameDMContext frameDmc, int regNo,
				String regName) {
			super(service.getSession().getId(), new IDMContext[] { frameDmc, group });
			fRegNo = regNo;
			fRegName = regName;
		}
        
        public int getRegNo() { return fRegNo; }
        public String getName() { return fRegName; }

        @Override
        public boolean equals(Object other) {
            return ((super.baseEquals(other)) && (((MIRegisterDMC) other).fRegNo == fRegNo) && 
                    (((MIRegisterDMC) other).fRegName.equals(fRegName)));
        }

        @Override
        public int hashCode() { return super.baseHashCode() ^ fRegNo; }
        @Override
        public String toString() { return baseToString() + ".register[" + fRegNo + "]"; } //$NON-NLS-1$ //$NON-NLS-2$
    }
    
    /*
     *  Event class to notify register value is changed
     */
    public static class RegisterChangedDMEvent implements IRegisters.IRegisterChangedDMEvent {

    	private final IRegisterDMContext fRegisterDmc;
    	
    	/**
		 * @since 4.6
		 */
    	public RegisterChangedDMEvent(IRegisterDMContext registerDMC) { 
    		fRegisterDmc = registerDMC;
        }
        
    	@Override
		public IRegisterDMContext getDMContext() {
			return fRegisterDmc;
		}
    }
    
    /**
     * Indicates a change in the list of Register groups e.g. after addition and removal
	 * @since 4.6
	 */
	public static class GroupsChangedDMEvent implements IRegisters.IGroupsChangedDMEvent {

		private final IContainerDMContext fGroupContainerDmc;

		public GroupsChangedDMEvent(IContainerDMContext containerDMC) {
			fGroupContainerDmc = containerDMC;
		}

		@Override
		public IContainerDMContext getDMContext() {
			return fGroupContainerDmc;
		}
	}

    /*
     *  Internal control variables.
     */
    
	private CommandFactory fCommandFactory;

    //One Group per container process
    private final Map<IContainerDMContext, MIRegisterGroupDMC> fContainerToGroupMap = new HashMap<IContainerDMContext, MIRegisterGroupDMC>();

    private CommandCache fRegisterNameCache;	 // Cache for holding the Register Names in the single Group
    private CommandCache fRegisterValueCache;  // Cache for holding the Register Values

    public MIRegisters(DsfSession session) 
    {
        super(session);
    }

    @Override
    protected BundleContext getBundleContext() 
    {
        return GdbPlugin.getBundleContext();
    }
    
    @Override
    public void initialize(final RequestMonitor requestMonitor) {
        super.initialize(
            new ImmediateRequestMonitor(requestMonitor) { 
                @Override
                protected void handleSuccess() {
                    doInitialize(requestMonitor);
                }});
    }
    
    private void doInitialize(RequestMonitor requestMonitor) {
        /*
         * Create the lower level register cache.
         */
    	ICommandControlService commandControl = getServicesTracker().getService(ICommandControlService.class);
		BufferedCommandControl bufferedCommandControl = new BufferedCommandControl(commandControl, getExecutor(), 2);
		
		fCommandFactory = getServicesTracker().getService(IMICommandControl.class).getCommandFactory();

		// This cache stores the result of a command when received; also, this cache
		// is manipulated when receiving events.  Currently, events are received after
		// three scheduling of the executor, while command results after only one.  This
		// can cause problems because command results might be processed before an event
		// that actually arrived before the command result.
		// To solve this, we use a bufferedCommandControl that will delay the command
		// result by two scheduling of the executor.
		// See bug 280461
        fRegisterValueCache = new CommandCache(getSession(), bufferedCommandControl);
        fRegisterValueCache.setContextAvailable(commandControl.getContext(), true);

        // This cache is not affected by events so does not need the bufferedCommandControl
        fRegisterNameCache  = new CommandCache(getSession(), commandControl);
        fRegisterNameCache.setContextAvailable(commandControl.getContext(), true);
               
        /*
         * Sign up so we see events. We use these events to decide how to manage
         * any local caches we are providing as well as the lower level register
         * cache we create to get/set registers on the target.
         */
        getSession().addServiceEventListener(this, null);
        
        /*
         * Make ourselves known so clients can use us.
         */
        register(new String[]{IRegisters.class.getName(), MIRegisters.class.getName()}, new Hashtable<String,String>());

        requestMonitor.done();
    }

    @Override
    public void shutdown(RequestMonitor requestMonitor) 
    {
        unregister();
        getSession().removeServiceEventListener(this);
        super.shutdown(requestMonitor);
    }

    public boolean isValid() { return true; }
    
	@Override
    public void getFormattedExpressionValue(FormattedValueDMContext dmc, DataRequestMonitor<FormattedValueDMData> rm) {
        if (dmc.getParents().length == 1 && dmc.getParents()[0] instanceof MIRegisterDMC) {
                getRegisterDataValue( (MIRegisterDMC) dmc.getParents()[0], dmc.getFormatID(), rm);
        } else {
            rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Unknown DMC type", null));  //$NON-NLS-1$
            rm.done();
        }
    }
    
	@Override
    public void getRegisterGroupData(IRegisterGroupDMContext regGroupDmc, DataRequestMonitor<IRegisterGroupDMData> rm) {
        /**
         * For the GDB GDBMI implementation there is only on group. The GPR and FPU registers are grouped into 
         * one set. We are going to hard wire this set as the value of ROOT_GROUP_NAME.
         */
        class RegisterGroupData implements IRegisterGroupDMData {
        	@Override
            public String getName() { return ROOT_GROUP_NAME; }
        	@Override
            public String getDescription() { return ROOT_GROUP_DESCRIPTION; }
        }

        rm.setData( new RegisterGroupData() ) ;
        rm.done();
    }

	@Override
    public void getBitFieldData(IBitFieldDMContext dmc, DataRequestMonitor<IBitFieldDMData> rm) {
        rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Bit fields not yet supported", null));  //$NON-NLS-1$
        rm.done();
    }
    
    /**
     * For the GDB GDBMI implementation there is only on group. We represent
     * this group as a single list we maintain within this service. So we
     * need to search this list to see if we have a current value.
     */
	@Override
    public void getRegisterData(IRegisterDMContext regDmc , final DataRequestMonitor<IRegisterDMData> rm) {
        if (regDmc instanceof MIRegisterDMC) {
            final MIRegisterDMC miRegDmc = (MIRegisterDMC)regDmc;
            final IFrameDMContext frameDmc = DMContexts.getAncestorOfType(regDmc, IFrameDMContext.class);
            // Create register data with name only e.g. not editable.
            if(frameDmc == null){
                rm.setData(new RegisterData(null, miRegDmc.getName(), BLANK_STRING, false));
                rm.done();
                return;
            }
            
            int[] regnos = {miRegDmc.getRegNo()};
            fRegisterValueCache.execute(
            	fCommandFactory.createMIDataListRegisterValues(frameDmc, MIFormat.HEXADECIMAL, regnos),
                new DataRequestMonitor<MIDataListRegisterValuesInfo>(getExecutor(), rm) {
                    @Override
                    protected void handleSuccess() {
                        // Retrieve the register value.
                        MIRegisterValue[] regValue = getData().getMIRegisterValues();
    
                        // If the list is empty just return empty handed.
                        // The only known case this happens is caused by a bug in GDB's Python
                        // scripts. See https://sourceware.org/bugzilla/show_bug.cgi?id=19637
                        // In this case, we know the register name anyway, we just guess that
                        // it is not floating point. The matching code in getRegisterDataValue()
                        // displays the error with link to a workaround
                        if (regValue.length == 0) {
                            rm.setData(new RegisterData(frameDmc, miRegDmc.getName(), BLANK_STRING, false));
                            rm.done();
                            return;
                        }
                        
                        // the request was for only one register
                        assert regValue.length == 1;
                        
                        // We can determine if the register is floating point because
                        // GDB returns this additional information as part of the value.
                        MIRegisterValue reg = regValue[0];
                        boolean isFloat = false;
                        
                        if ( reg.getValue().contains("float")) { //$NON-NLS-1$
                            isFloat = true;
                        }

                        // Return the new register attributes.
                        rm.setData(new RegisterData(frameDmc, miRegDmc.getName(), BLANK_STRING, isFloat));
                        rm.done();
                    }
                });
        } else {
            rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Unknown DMC type", null));  //$NON-NLS-1$
            rm.done();
        }
    }
    
    private void getRegisterDataValue( final MIRegisterDMC regDmc, final String formatId, final DataRequestMonitor<FormattedValueDMData> rm) {
    	IFrameDMContext frameDmc = DMContexts.getAncestorOfType(regDmc, IFrameDMContext.class);
        if(frameDmc == null){
            // Set value to blank if frame dmc is not present
            rm.setData( new FormattedValueDMData( BLANK_STRING ) );
            rm.done();
            return;
        }

        // Select the format to be shown
        int NumberFormat = MIFormat.HEXADECIMAL;
        
        if ( HEX_FORMAT.equals    ( formatId ) ) { NumberFormat = MIFormat.HEXADECIMAL; }
        if ( OCTAL_FORMAT.equals  ( formatId ) ) { NumberFormat = MIFormat.OCTAL; }
        if ( NATURAL_FORMAT.equals( formatId ) ) { NumberFormat = MIFormat.NATURAL; }
        if ( BINARY_FORMAT.equals ( formatId ) ) { NumberFormat = MIFormat.BINARY; }
        if ( DECIMAL_FORMAT.equals( formatId ) ) { NumberFormat = MIFormat.DECIMAL; }
        
        int[] regnos = {regDmc.getRegNo()};
        fRegisterValueCache.execute(
        	fCommandFactory.createMIDataListRegisterValues(frameDmc, NumberFormat, regnos),
            new DataRequestMonitor<MIDataListRegisterValuesInfo>(getExecutor(), rm) {
                @Override
                protected void handleSuccess() {
                    // Retrieve the register value.
                    MIRegisterValue[] regValue = getData().getMIRegisterValues();

                    // If the list is empty just return empty handed.
                    // The only known case this happens is caused by a bug in GDB's Python
                    // scripts. See https://sourceware.org/bugzilla/show_bug.cgi?id=19637
                    // In the display data, we show link to Eclipse Bugzilla entry which has
                    // a comment on how to fix this manually.
                    if (regValue.length == 0) {
                        rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID,
                                IDsfStatusConstants.REQUEST_FAILED,
                                "Encountered a GDB Error See http://eclip.se/506382#c7 for workarounds", null)); //$NON-NLS-1$
                        rm.done();
                        return;
                    }

                    MIRegisterValue reg = regValue[0];

                    // Return the new register value.
                    rm.setData( new FormattedValueDMData( reg.getValue() ) );
                    rm.done();
                }
            });
    }
        
    /**
	 * @since 4.6
	 */
    protected static class RegisterData implements IRegisterDMData {
    	
        final private String fRegName;
        final private String fRegDesc;
        final private boolean fIsFloat;
        final private IFrameDMContext fContext;
    	
    	public RegisterData(IFrameDMContext context, String regName, String regDesc, boolean isFloat ) {
    		
            fRegName = regName;
            fRegDesc = regDesc;
            fIsFloat = isFloat;
            fContext = context;
    	}
    	
    	@Override
    	public boolean isReadable() { return true; }
    	@Override
        public boolean isReadOnce() { return false; }
    	@Override
		public boolean isWriteable() {
    		//The absence of the frame context is likely the result of a selection not specific to a stack frame
    		//e.g. the selection of a running thread, or the selection of a process.
    		//So, an instance with no stack frame is not writable
			return (fContext != null);
		}
    	@Override
        public boolean isWriteOnce() { return false; }
    	@Override
        public boolean hasSideEffects() { return false; }
    	@Override
        public boolean isVolatile() { return true; }

    	@Override
        public boolean isFloat() { return fIsFloat; }
    	@Override
        public String getName() { return fRegName; }
    	@Override
        public String getDescription() { return fRegDesc; }
    }

    // Wraps a list of registers in DMContexts.
    private MIRegisterDMC[] makeRegisterDMCs(MIRegisterGroupDMC groupDmc, String[] regNames) {
    	return makeRegisterDMCs(groupDmc, null, regNames);
    }
    
    // Wraps a list of registers in DMContexts.
    private MIRegisterDMC[] makeRegisterDMCs(MIRegisterGroupDMC groupDmc, IFrameDMContext frameDmc, String[] regNames) {
        List<MIRegisterDMC> regDmcList = new ArrayList<MIRegisters.MIRegisterDMC>( regNames.length );
        int regNo = 0;
        for (String regName : regNames) {
            if(regName != null && !regName.isEmpty()) {
            	if(frameDmc != null)
            		regDmcList.add(new MIRegisterDMC(this, groupDmc, frameDmc, regNo, regName));
            	else
            		regDmcList.add(new MIRegisterDMC(this, groupDmc, regNo, regName));
            }
            regNo++;
        }
        return regDmcList.toArray(new MIRegisterDMC[regDmcList.size()]);
    }

    /*
     *   Event handling section. These event handlers control the caching state of the
     *   register caches. This service creates several cache objects. Not all of which
     *   need to be flushed. These handlers maintain the state of the caches.
     */

    /**
     * @nooverride This method is not intended to be re-implemented or extended by clients.
     * @noreference This method is not intended to be referenced by clients.
     */
    @DsfServiceEventHandler 
    public void eventDispatched(IRunControl.IResumedDMEvent e) {
        fRegisterValueCache.setContextAvailable(e.getDMContext(), false);
        if (e.getReason() != StateChangeReason.STEP) {
            fRegisterValueCache.reset();
        }
    }
    
    /**
     * @nooverride This method is not intended to be re-implemented or extended by clients.
     * @noreference This method is not intended to be referenced by clients.
     */
    @DsfServiceEventHandler 
    public void eventDispatched(
    IRunControl.ISuspendedDMEvent e) {
        fRegisterValueCache.setContextAvailable(e.getDMContext(), true);
        fRegisterValueCache.reset();
    }

    /**
     * @nooverride This method is not intended to be re-implemented or extended by clients.
     * @noreference This method is not intended to be referenced by clients.
     */
    @DsfServiceEventHandler 
    public void eventDispatched(final IRegisters.IRegisterChangedDMEvent e) {
    	fRegisterValueCache.reset();
    }
    
    /**
	 * @since 4.6
	 */
    protected void generateRegisterChangedEvent(IRegisterDMContext dmc ) {
        getSession().dispatchEvent(new RegisterChangedDMEvent(dmc), getProperties());

        // Fix for Bug 400840
        // When one register is modified, it could affect other registers.
        // To properly reflect that, we send a change for all registers.
        // We cheat by pretending the group has changed, since we know there is 
        // only one group.
        // This method can be extended by group managers to propagate the event as needed 
        final IRegisterGroupDMContext groupDmc = DMContexts.getAncestorOfType(dmc, IRegisterGroupDMContext.class);
        if (groupDmc != null) {
        	IRegistersChangedDMEvent event = new IRegistersChangedDMEvent() {
        		@Override
        		public IRegisterGroupDMContext getDMContext() {
        			return groupDmc;
        		}
        	};
        	getSession().dispatchEvent(event, getProperties());
        }        
    }
    
    /*
     * These are the public interfaces for this service.
     * 
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.debug.service.IRegisters#getRegisterGroups(org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext, org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
     */
	@Override
    public void getRegisterGroups(IDMContext ctx, DataRequestMonitor<IRegisterGroupDMContext[]> rm ) {
    	IContainerDMContext contDmc = DMContexts.getAncestorOfType(ctx, IContainerDMContext.class);
        if (contDmc == null) {
            rm.setStatus( new Status( IStatus.ERROR , GdbPlugin.PLUGIN_ID , INVALID_HANDLE , "Container context not found", null ) ) ;   //$NON-NLS-1$
            rm.done();
            return;
        }
        
        //Bug 418176
        //Only one group per Process (container) is supported in the implementation of this class
        MIRegisterGroupDMC registerGroup = fContainerToGroupMap.get(contDmc);
        
		if (registerGroup == null) {
			registerGroup = new MIRegisterGroupDMC(this, contDmc, 0, ROOT_GROUP_NAME);
			fContainerToGroupMap.put(contDmc, registerGroup);
		}
        
        MIRegisterGroupDMC[] groupDMCs = new MIRegisterGroupDMC[] { registerGroup };

        rm.setData(groupDMCs) ;
        rm.done() ;
    }
    
    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.debug.service.IRegisters#getRegisters(org.eclipse.cdt.dsf.debug.service.IRegisters.IRegisterGroupDMContext, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
     */
	@Override
	public void getRegisters(final IDMContext dmc, final DataRequestMonitor<IRegisterDMContext[]> rm) {
		final MIRegisterGroupDMC groupDmc = DMContexts.getAncestorOfType(dmc, MIRegisterGroupDMC.class);
		if (groupDmc == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE,
					"RegisterGroup context not found", null)); //$NON-NLS-1$
			rm.done();
			return;
		}

		final IContainerDMContext containerDmc = DMContexts.getAncestorOfType(dmc, IContainerDMContext.class);
		if (containerDmc == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE,
					"Container context not found", null)); //$NON-NLS-1$
			rm.done();
			return;
		}

		// There is only one group and its number must be 0.
		if (groupDmc.getGroupNo() == 0) {
			final IFrameDMContext frameDmc = DMContexts.getAncestorOfType(dmc, IFrameDMContext.class);
			if (frameDmc == null) {
				// The selection does not provide a specific frame, then resolve the top frame on the current thread
				IMIExecutionDMContext execDmc = DMContexts.getAncestorOfType(dmc, IMIExecutionDMContext.class);
				if (execDmc != null) {
					IStack stackService = getServicesTracker().getService(IStack.class);
					if (stackService != null) {
						stackService.getTopFrame(execDmc, new ImmediateDataRequestMonitor<IStack.IFrameDMContext>(rm) {
							@Override
							protected void handleSuccess() {
								getRegisters(getData(), groupDmc, containerDmc, rm);
							}
							
							@Override
							protected void handleFailure() {
								//Unable to resolve top frame on current thread.
								//The thread could e.g. be in running state, 
								//we return register instances with no associated execution context
								//i.e. unable to resolve its associated value.
								getRegisters(null, groupDmc, containerDmc, rm);
							}
						});

						return;
					}
				}
			}

			getRegisters(frameDmc, groupDmc, containerDmc, rm);

		} else {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR,
					"Invalid group = " + groupDmc, null)); //$NON-NLS-1$
			rm.done();
		}
	}
    
	private void getRegisters(final IFrameDMContext frameDmc, final MIRegisterGroupDMC groupDmc,
			IContainerDMContext containerDmc, final DataRequestMonitor<IRegisterDMContext[]> rm) {

		fRegisterNameCache.execute(fCommandFactory.createMIDataListRegisterNames(containerDmc),
				new ImmediateDataRequestMonitor<MIDataListRegisterNamesInfo>(rm) {
					@Override
					protected void handleSuccess() {
						// Retrieve the register names.
						String[] regNames = getData().getRegisterNames();

						// If the list is empty just return empty handed.
						if (regNames.length == 0) {
							rm.done();
							return;
						}

						if (frameDmc == null)
							// The selection does not provide a frame or thread context,
							// This can happen e.g. if a container /process is selected
							// Lets provide the list of register names applicable to the selected process
							// i.e. instances with only name information which can not resolve a value
							rm.setData(makeRegisterDMCs(groupDmc, regNames));
						else
							rm.setData(makeRegisterDMCs(groupDmc, frameDmc, regNames));
						rm.done();
					}
				});
	}
	
    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.debug.service.IRegisters#getBitFields(org.eclipse.cdt.dsf.debug.service.IRegisters.IRegisterDMContext, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
     */
	@Override
    public void getBitFields( IDMContext regDmc , DataRequestMonitor<IBitFieldDMContext[]> rm ) {
        rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "BitField not supported", null)); //$NON-NLS-1$
        rm.done();
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.debug.service.IRegisters#writeRegister(org.eclipse.cdt.dsf.debug.service.IRegisters.IRegisterDMContext, java.lang.String, java.lang.String, org.eclipse.cdt.dsf.concurrent.RequestMonitor)
     */
	@Override
	public void writeRegister(IRegisterDMContext regCtx, final String regValue, final String formatId,
			final RequestMonitor rm) {

		assert(regCtx instanceof MIRegisterDMC);
		final MIRegisterDMC regDmc = (MIRegisterDMC) regCtx;

		final IExpressions exprService = getServicesTracker().getService(IExpressions.class);
		String regName = regDmc.getName();
		final IExpressionDMContext exprCtxt = exprService.createExpression(regCtx, "$" + regName); //$NON-NLS-1$

		final FormattedValueDMContext valueDmc = exprService.getFormattedValueContext(exprCtxt, formatId);
		exprService.getFormattedExpressionValue(valueDmc, new DataRequestMonitor<FormattedValueDMData>(getExecutor(), rm) {
			@Override
			protected void handleSuccess() {
				if (!regValue.equals(getData().getFormattedValue()) || !valueDmc.getFormatID().equals(formatId)) {
					exprService.writeExpression(exprCtxt, regValue, formatId, new DataRequestMonitor<MIInfo>(getExecutor(), rm) {
						@Override
						protected void handleSuccess() {
							generateRegisterChangedEvent(regDmc);
							rm.done();
						}
					});
				}// if
				else {
					rm.done();
				}
			}// handleSuccess
		});
	}

    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.debug.service.IRegisters#writeBitField(org.eclipse.cdt.dsf.debug.service.IRegisters.IBitFieldDMContext, java.lang.String, java.lang.String, org.eclipse.cdt.dsf.concurrent.RequestMonitor)
     */
	@Override
    public void writeBitField(IBitFieldDMContext bitFieldCtx, String bitFieldValue, String formatId, RequestMonitor rm) {
        rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Writing bit field not supported", null)); //$NON-NLS-1$
        rm.done();
    }
    
    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.debug.service.IRegisters#writeBitField(org.eclipse.cdt.dsf.debug.service.IRegisters.IBitFieldDMContext, org.eclipse.cdt.dsf.debug.service.IRegisters.IMnemonic, org.eclipse.cdt.dsf.concurrent.RequestMonitor)
     */
	@Override
    public void writeBitField(IBitFieldDMContext bitFieldCtx, IMnemonic mnemonic, RequestMonitor rm) {
        rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Writing bit field not supported", null)); //$NON-NLS-1$
        rm.done();
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.debug.service.IFormattedValues#getAvailableFormats(org.eclipse.cdt.dsf.debug.service.IFormattedValues.IFormattedDataDMContext, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
     */
	@Override
    public void getAvailableFormats(IFormattedDataDMContext dmc, DataRequestMonitor<String[]> rm) {
        
        rm.setData(new String[] { HEX_FORMAT, DECIMAL_FORMAT, OCTAL_FORMAT, BINARY_FORMAT, NATURAL_FORMAT });
        rm.done();
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.debug.service.IFormattedValues#getFormattedValueContext(org.eclipse.cdt.dsf.debug.service.IFormattedValues.IFormattedDataDMContext, java.lang.String)
     */
	@Override
    public FormattedValueDMContext getFormattedValueContext(IFormattedDataDMContext dmc, String formatId) {
        if ( dmc instanceof MIRegisterDMC ) {
            MIRegisterDMC regDmc = (MIRegisterDMC) dmc;
            return( new FormattedValueDMContext( this, regDmc, formatId));
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.debug.service.IRegisters#findRegisterGroup(org.eclipse.cdt.dsf.datamodel.IDMContext, java.lang.String, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
     */
	@Override
    public void findRegisterGroup(IDMContext ctx, String name, DataRequestMonitor<IRegisterGroupDMContext> rm) {
        rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Finding a Register Group context not supported", null)); //$NON-NLS-1$
        rm.done();
    }
    
    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.debug.service.IRegisters#findRegister(org.eclipse.cdt.dsf.datamodel.IDMContext, java.lang.String, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
     */
	@Override
    public void findRegister(IDMContext ctx, String name, DataRequestMonitor<IRegisterDMContext> rm) {
        getRegisters(ctx, new ImmediateDataRequestMonitor<IRegisterDMContext[]>() {
            @Override
            protected void handleSuccess () {
                IRegisterDMContext[] allRegs = getData();
                
                // in all registers found, look for one with the name we seek
                for (int i = 0; i < allRegs.length; i++) {
                    if (allRegs[i] instanceof MIRegisterDMC) {
                        if (name.equals(((MIRegisterDMC)allRegs[i]).getName())) {
                            // found it
                            rm.done(allRegs[i]);
                            return;
                        }
                    }
                }

                // register was not found
                rm.done(new Status(IStatus.WARNING, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Unknown register name", null)); //$NON-NLS-1$
                return;
            }
        });
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.cdt.dsf.debug.service.IRegisters#findBitField(org.eclipse.cdt.dsf.datamodel.IDMContext, java.lang.String, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
     */
	@Override
    public void findBitField(IDMContext ctx, String name, DataRequestMonitor<IBitFieldDMContext> rm) {
        rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Finding a Register Group context not supported", null)); //$NON-NLS-1$
        rm.done();
    }
    
    /**
     * {@inheritDoc}
     * @since 1.1
     */
	@Override
    public void flushCache(IDMContext context) {
        fRegisterNameCache.reset(context);
        fRegisterValueCache.reset(context);
    }
}
